/*
 * @Description:
 * @Autor: dongjunhua
 * @Date: 2020-12-21 11:37:15
 * @LastEditors: dongjunhua
 * @LastEditTime: 2021-10-14 14:31:19
 */

import Subject from '@/core/model/Subject';
import AuthenticationToken from '@/core/model/AuthenticationToken';
import AuthenticationInfo from '@/core/model/AuthenticationInfo';
import AuthorizationInfo from '@/core/model/AuthorizationInfo';
import Realm from '@/core/base/Realm';
import Storage from '@/utils/Storage';
import { moduleRoutes, addFrameRoute, getOriginalPath } from '@/router';
import { loginType, ssoAddress, loginValidDuration } from '@/config';
import router from '@/router';
import { ElNotification } from 'element-plus';

const state = new Subject(loginType, ssoAddress);

const mutations = {
  SET_INITIALIZED: (state, initialized) => {
    state.initialized = initialized;
  },
  SET_SSO_ADDRESS: (state, ssoAddress) => {
    state.ssoAddress = ssoAddress;
  },
  SET_ACCOUNT: (state, account) => {
    state.account = account;
  },
  SET_USER: (state, user) => {
    state.user = user;
  },
  SET_IS_AUTHENTICATED: (state, isAuthenticated) => {
    state.isAuthenticated = isAuthenticated;
  },
  SET_IS_PERMITTED: (state, isPermitted) => {
    state.isPermitted = isPermitted;
  },
  SET_PERMITTED_ROUTES: (state, permittedRoutes) => {
    state.permittedRoutes = permittedRoutes;
  },
  SET_TOKEN: (state, token) => {
    state.token = token;
  }
};

const afterStorageChange = () => {
  const subject = Storage.getLocal('subject');
  if (!subject) {
    router.go(0);
  }
};

const eachMenu = (menuList = [], callback = () => {}) => {
  menuList.forEach(m => {
    callback(m);
    m.children && m.children.length > 0 && eachMenu(m.children, callback);
  });
};

const permitRoutes = (menuList = []) => {
  const permittedRoutes = [];
  if (menuList.length === 0) {
    return [];
  }
  eachMenu(menuList, m => {
    if (!m.routingUrl) {
      return;
    }
    if (m.redirectUrl) {
      addFrameRoute(m);
      permittedRoutes.push(m.routingUrl);
    } else {
      const childrenPath = ['/micro1', '/micro2'];
      if (
        moduleRoutes.findIndex(r => r.path === m.routingUrl) > -1 ||
        childrenPath.some(item => item.includes(m.routingUrl))
      ) {
        permittedRoutes.push(m.routingUrl);
      }
    }
  });
  return permittedRoutes;
};

const actions = {
  /**
   * @method login 身份认证
   * @param {*} context
   * @param {*} token
   * @return {Promise}
   */
  login: async ({ commit, state }, token) => {
    try {
      // token must be an AuthenticationToken instance when use default login mode
      // 当使用默认登录模式时，token必须是一个AuthenticationToken的实例
      // 当使用单点登录模式时，token应该是一个由单点登录服务端颁发的令牌（授权码）
      if (
        state.loginType === 'default' &&
        token.constructor !== AuthenticationToken
      ) {
        throw new Error('Invalid Token');
      }
      // get authentication info from realm
      // 从安全数据源获得身份认证信息
      const info = await Realm.doGetAuthenticationInfo(token);
      if (info.constructor !== AuthenticationInfo) {
        throw new Error('Invalid AuthenticationInfo');
      }
      commit('SET_INITIALIZED', false);
      commit('SET_ACCOUNT', info.account);
      commit('SET_IS_AUTHENTICATED', true);
      Storage.setLocal('subject', state, info.expired || loginValidDuration);
      // add storage listener
      // 添加storage监听器，用于在任意标签页会话失效时触发其余标签页更新
      Storage.addListener(afterStorageChange);
    } catch (error) {
      ElNotification({
        message: error,
        type: 'error',
        duration: 5 * 1000
      });
    }
  },
  /**
   * @method permit 授权
   * @param {*} context
   * @param {object} payload
   * @return {Promise}
   */
  permit: async ({ commit, state }, payload = {}) => {
    try {
      let { authorizationToken, afterPermitted } = payload;
      typeof afterPermitted !== 'function' &&
        (afterPermitted = () => {
          router.push('/i');
        });
      const info = await Realm.doGetAuthorizationInfo(authorizationToken);
      if (info.constructor !== AuthorizationInfo) {
        throw new Error('Invalid AuthorizationInfo');
      }
      const permittedRoutes = permitRoutes(info.user.menuList);
      commit('SET_USER', info.user);
      commit('SET_IS_PERMITTED', true);
      commit('SET_PERMITTED_ROUTES', permittedRoutes);
      commit('SET_TOKEN', info.token);
      Storage.setLocal('subject', state);
      // permit complete, do redirect or custom action
      // 授权完成，执行默认的跳转或一个自定义的回调
      afterPermitted(info.token);
    } catch (error) {
      ElNotification({
        message: error,
        type: 'error',
        duration: 5 * 1000
      });
    }
  },
  /**
   * @method permit 遍历菜单
   * @param {*} context
   * @param {Function} payload
   */
  eachMenu: ({ state }, payload = () => {}) => {
    if (state.user && state.user.menuList) {
      eachMenu(state.user.menuList, payload);
    }
  },
  /**
   * @method checkPermission 校验路由权限
   * @param {*} context
   * @param {object} payload
   * @return {boolean}
   */
  checkPermission: ({ state }, payload = {}) => {
    let { path, whiteList = [] } = payload;
    if (!path) {
      return false;
    }
    if (whiteList.includes(path)) {
      return true;
    }
    path = getOriginalPath(path);
    return state.permittedRoutes.some(item => path.indexOf(item) === 0);
  },
  /**
   * @method recovery 恢复会话
   * @param {*} context
   */
  recovery: ({ commit }) => {
    // get persistence data from storage
    // 尝试从storage中恢复前端会话
    const subject = Storage.getLocal('subject');
    commit('SET_INITIALIZED', false);
    if (!subject) {
      commit('SET_IS_AUTHENTICATED', false);
      return;
    }
    commit('SET_ACCOUNT', subject.account);
    commit('SET_IS_AUTHENTICATED', subject.isAuthenticated);
    commit('SET_USER', subject.user);
    commit('SET_IS_PERMITTED', subject.isPermitted);
    commit('SET_PERMITTED_ROUTES', subject.permittedRoutes);
    commit('SET_TOKEN', subject.token);
    Storage.addListener(afterStorageChange);
  },
  /**
   * @method logout 注销
   * @param {*} context
   * @param {object} payload
   * @return {Promise}
   */
  logout: async ({ commit, dispatch, state }, payload = {}) => {
    let {
      beforeLogout,
      ssoAddress,
      ssoLogout = false,
      query = {},
      doRouterPush = true
    } = payload;
    // action before logout
    typeof beforeLogout !== 'function' && (beforeLogout = Realm.beforeLogout);
    const realmSsoAddress = await beforeLogout();
    // ssoAddress priority: option > relam > setting
    realmSsoAddress && commit('SET_SSO_ADDRESS', realmSsoAddress);
    ssoAddress && commit('SET_SSO_ADDRESS', ssoAddress);
    // remove storage listener
    Storage.removeListener(afterStorageChange);
    // clear storage
    Storage.delLocal('subject');
    Storage.delSession();
    // reset subject
    commit('SET_ACCOUNT', {});
    commit('SET_IS_AUTHENTICATED', false);
    commit('SET_USER', {});
    commit('SET_IS_PERMITTED', false);
    commit('SET_PERMITTED_ROUTES', []);
    // clear tagsView
    dispatch('app/clearTagView', null, { root: true });
    // redirect
    const redirect =
      state.loginType === 'sso' && state.ssoAddress
        ? {
            path: '/auth-redirect',
            query: { ssoAddress: state.ssoAddress, ssoLogout }
          }
        : { path: '/login', query };
    if (doRouterPush) {
      router.push(redirect);
    } else {
      return redirect;
    }
  }
};

export default {
  namespaced: true,
  state,
  mutations,
  actions
};
